/*
 * Copyright 2014 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "include/utils/SkEventTracer.h"

#include "include/private/base/SkOnce.h"
#include "utils/SkTraceEventPhase.h"
#include <hitrace/trace.h>
#include <stdlib.h>
#include <atomic>
#include <string>

class SkDefaultEventTracer : public SkEventTracer {
    SkEventTracer::Handle
        addTraceEvent(char phase,
                      const uint8_t* categoryEnabledFlag,
                      const char* name,
                      uint64_t id,
                      int numArgs,
                      const char** argNames,
                      const uint8_t* argTypes,
                      const uint64_t* argValues,
                      uint8_t flags) override { return 0; }

    void
        updateTraceEventDuration(const uint8_t* categoryEnabledFlag,
                                 const char* name,
                                 SkEventTracer::Handle handle) override {}

    const uint8_t* getCategoryGroupEnabled(const char* name) override {
        static uint8_t no = 0;
        return &no;
    }
    const char* getCategoryGroupName(
      const uint8_t* categoryEnabledFlag) override {
        static const char* stub = "stub";
        return stub;
    }

    // The default tracer does not yet support splitting up trace output into sections.
    void newTracingSection(const char* name) override {}
};

class SkOHEventTracer : public SkDefaultEventTracer {
    SkEventTracer::Handle addTraceEvent(char phase, const uint8_t *categoryEnabledFlag, const char *name, uint64_t id,
                                        int numArgs, const char **argNames, const uint8_t *argTypes,
                                        const uint64_t *argValues, uint8_t flags) override {
        std::string traceName((const char *)categoryEnabledFlag);
        traceName.append(":");
        traceName.append(name);

        if (argNames != nullptr && numArgs != 0) {
            for (int i = 0; i < numArgs; i++) {
                traceName += ":" + std::string(argNames[i]) + "-" + std::to_string(argValues[i]);
            }
        }

        if (TRACE_EVENT_PHASE_COMPLETE == phase || TRACE_EVENT_PHASE_INSTANT == phase ||
            TRACE_EVENT_PHASE_BEGIN == phase) {
            OH_HiTrace_StartTrace(traceName.c_str());
        }

        if (TRACE_EVENT_PHASE_INSTANT == phase || TRACE_EVENT_PHASE_END == phase) {
            OH_HiTrace_FinishTrace();
        }
        return phase;
    }

    void updateTraceEventDuration(const uint8_t *categoryEnabledFlag, const char *name,
                                  SkEventTracer::Handle handle) override {
        char phase = (char)handle;
        if (TRACE_EVENT_PHASE_COMPLETE == phase) {
            OH_HiTrace_FinishTrace();
        }
    }

    const uint8_t *getCategoryGroupEnabled(const char *name) override {
        static uint8_t yes = SkEventTracer::kEnabledForRecording_CategoryGroupEnabledFlags;
        return &yes;
    }
    const char *getCategoryGroupName(const uint8_t *categoryEnabledFlag) override {
        static const char *stub = "skiaOHTrace";
        return stub;
    }

    // The default tracer does not yet support splitting up trace output into sections.
    void newTracingSection(const char *name) override {}
};

// We prefer gUserTracer if it's been set, otherwise we fall back on a default tracer;
static std::atomic<SkEventTracer*> gUserTracer{nullptr};

bool SkEventTracer::SetInstance(SkEventTracer* tracer, bool leakTracer) {
    SkEventTracer* expected = nullptr;
    if (!gUserTracer.compare_exchange_strong(expected, tracer)) {
        delete tracer;
        return false;
    }
    // If leaking the tracer is accepted then there is no need to install
    // the atexit.
    if (!leakTracer) {
        atexit([]() { delete gUserTracer.load(); });
    }
    return true;
}

SkEventTracer* SkEventTracer::GetInstance() {
    if (auto tracer = gUserTracer.load(std::memory_order_acquire)) {
        return tracer;
    }
    static SkOnce once;
    static SkDefaultEventTracer* defaultTracer;
#ifdef SK_DEBUG
    once([] { defaultTracer = new SkOHEventTracer; });
#else
    once([] { defaultTracer = new SkDefaultEventTracer; });
#endif
    return defaultTracer;
}
